// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.


#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#include "CangjieDemangle.h"
#include "Demangler.h"
#include "StdString.h"

using namespace Cangjie;

namespace {
std::map<std::string, std::string> obfNames;

static void Println(const std::string& msg) { std::cout << msg << std::endl; }

void ReadMapFile(const std::string& name)
{
    std::string line;
    std::ifstream file(name);
    const int elemNum = 2;

    if (!file.is_open()) {
        Println("Cannot open file " + name);
        return;
    }

    while (getline(file, line)) {
        std::istringstream iss(line);
        std::string obfName;
        std::string tmp;

        for (int i = 0; i < elemNum; i++) {
            if (!getline(iss, tmp, ' ')) {
                break;
            }
            switch (i) {
                case 0:
                    obfName = tmp;
                    break;
                case 1:
                    obfNames[obfName] = tmp;
                    break;
                default:
                    break;
            }
        }
    }

    file.close();
}

void ReadMapFiles(const std::string& names)
{
    std::istringstream iss(names);
    std::string name;
    while (getline(iss, name, ',')) {
        ReadMapFile(name);
    }
}

void Usage()
{
    Println("Usage:");
    Println("\tcjfilt [option] name1 name2 ...");
    Println("\tIf there is a '$' in a mangled name, use '' to quote it.");
    Println("");
    Println("Options:");
    Println("\t-l\t\tlist detailed information");
    Println("\t-T\t\tdemangle type name");
    Println("\t-f\t\tsymbol mapping files generated by obfuscator");
}

bool CheckOption(const std::vector<std::string>& args, const std::string& option)
{
    for (const auto& arg : args) {
        if (arg == option) {
            return true;
        }
    }
    return false;
}

void Demangle(const std::string& mangledName, bool isDetailed)
{
    auto demangler = Demangler<Cangjie::StdString>(mangledName.c_str(), '.');
    Println(demangler.ScopeResolution());
    auto di = demangler.Demangle();
    std::string pkgName = std::string(di.GetPkgName().Str());
    std::string demangledName =
        pkgName + std::string(pkgName.empty() ? "" : ".") + di.GetFullName(demangler.ScopeResolution()).Str();
    Println("demangled:\t\t" + demangledName);
    if (isDetailed) {
        auto identifier = std::string(di.GetIdentifier().Str());
        auto argsInfo = std::string(di.GetArgTypesName().Str());
        Println("package:\t\t" + pkgName);
        Println("identifier:\t\t" + identifier);
        Println("args:\t\t\t" + argsInfo);
        Println("validation:\t\t" + std::string(di.IsValid() ? "valid" : "invalid"));
        Println("is private declaration:\t" + std::string(di.isPrivateDeclaration ? "yes" : "no"));
    }
}

void DemangleType(const std::string& mangledName, bool isDetailed)
{
    auto demangler = Demangler<Cangjie::StdString>(mangledName.c_str(), '.');
    auto di = demangler.Demangle(true);
    std::string demangledName{ di.GetFullName(demangler.ScopeResolution()).Str() };
    Println("demangled:\t\t" + demangledName);
    if (isDetailed) {
        std::string pkgName = std::string(di.GetPkgName().Str());
        auto identifier = std::string(di.GetIdentifier().Str());
        auto argsInfo = std::string(di.GetArgTypesName().Str());
        Println("package:\t\t" + pkgName);
        Println("identifier:\t\t" + identifier);
        Println("args:\t\t\t" + argsInfo);
        Println("validation:\t\t" + std::string(di.IsValid() ? "valid" : "invalid"));
    }
}
} // namespace

int main(int argc, char* argv[])
{
    std::vector<std::string> args;
    for (int i = 0; i < argc; ++i) {
        if (!argv[i]) {
            continue;
        }
        args.emplace_back(argv[i]);
    }

    if (args.size() <= 1) {
        Println("no arguments");
        return 1;
    }

    if (CheckOption(args, "-h")) {
        Usage();
        return 0;
    }

    bool isDetailed = CheckOption(args, "-l");
    bool isObfuscated = CheckOption(args, "-f");
    bool isType = CheckOption(args, "-T");
    for (int i = 1; i < args.size(); ++i) {
        if (args[i] == "-l" || args[i] == "-f" || args[i] == "-T") {
            continue;
        }

        if (isObfuscated && i > 1 && args[i - 1] == "-f") {
            ReadMapFiles(args[i]);
            continue;
        }

        if (isObfuscated && obfNames.find(args[i]) != obfNames.end()) {
            Demangle(obfNames[args[i]], isDetailed);
        } else if (isType) {
            DemangleType(args[i], isDetailed);
        } else {
            Demangle(args[i], isDetailed);
        }
    }
    return 0;
}
